package com.yungu.swift.admin.route.common;

import com.alibaba.dubbo.config.annotation.Reference;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.yungu.swift.admin.consts.AdminErrorEnum;
import com.yungu.swift.admin.route.helper.JwtHelper;
import com.yungu.swift.admin.utils.NetworkUtil;
import com.yungu.swift.admin.utils.RandomCipherUtils;
import com.yungu.swift.autoconfig.redis.RedisCacheService;
import com.yungu.swift.autoconfig.redis.RedisStringCacheService;
import com.yungu.swift.base.ResponseData;
import com.yungu.swift.base.model.admin.AdminParam;
import com.yungu.swift.common.model.dto.CommonIdentifyDto;
import com.yungu.swift.common.model.param.AdminIdentifyParam;
import com.yungu.swift.common.service.CommonSmsService;
import com.yungu.swift.constants.CommonConstant;
import com.yungu.swift.constants.MagicConstant;
import com.yungu.swift.system.sys.model.dto.SysResourceDto;
import com.yungu.swift.system.sys.model.dto.SysUserDto;
import com.yungu.swift.system.sys.model.dto.SysUserOperateLogDto;
import com.yungu.swift.system.sys.model.param.AdminLoginParam;
import com.yungu.swift.system.sys.model.param.AdminRefreshTokenParam;
import com.yungu.swift.system.sys.model.vo.AdminLoginVo;
import com.yungu.swift.system.sys.model.vo.BusinessVo;
import com.yungu.swift.system.sys.service.SysBusinessOrganizationService;
import com.yungu.swift.system.sys.service.SysResourceService;
import com.yungu.swift.system.sys.service.SysUserOperateLogService;
import com.yungu.swift.system.sys.service.SysUserService;
import com.yungu.swift.utils.JsonUtils;
import com.yungu.swift.utils.MapUtils;
import com.yungu.swift.utils.Md5Utils;
import com.yungu.swift.utils.ParamUtil;
import com.yungu.swift.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.yungu.swift.admin.config.ApplicationConfig.SERVER_CONFIG;

/**
 * 登陆管理
 *
 * @author : cuixiuyin
 * @date : 2019/6/13
 */
@Slf4j
@RestController
@RequestMapping("/admin/v1/common")
@Api(value = "【通用服务】", tags = "登陆/登出相关接口")
public class LogicAdmin {

    @Reference
    private SysUserService sysUserService;
    @Reference
    private SysResourceService sysResourceService;
    @Reference
    private CommonSmsService commonSmsService;
    @Reference
    private SysUserOperateLogService sysUserOperateLogService;
    @Reference
    private SysBusinessOrganizationService sysBusinessOrganizationService;

    @Autowired
    private RedisStringCacheService redisStringCacheService;
    @Autowired
    private RedisCacheService redisCacheService;

    private static final String LOGIN_ERROR_PREFIX = "LOGIN_ERROR_COUNT_";
    private static final String LOGIN_USER_PREFIX = "LOGIN_USER_";
    public static final String ACCESS_TOKEN_PREFFIX = "ACCESS_TOKEN_";
    public static final String REFRESH_TOKEN_PREFFIX = "REFRESH_TOKEN_";

    /**
     * 访问 token 过期时间 7200 秒（2小时、120 分钟）
     */
    private static final Integer ACCESS_TOKEN_EXPIRATION = 2 * 60;
    /**
     * 刷新 token 过期时间 28800 秒（6小时、360 分钟）
     */
    private static final Integer REFRESH_TOKEN_EXPIRATION = 6 * 60;

    private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("login-pool-%d").build();

    private ExecutorService POOL = new ThreadPoolExecutor(5, 200, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());


    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ApiOperation(value = "用户登陆接口", httpMethod = "POST")
    public ResponseData<AdminLoginVo> login(@RequestBody AdminLoginParam loginParam, HttpServletRequest request) {
        if (ParamUtil.isIllegal(loginParam.getUserAccount(), loginParam.getPassword())) {
            return ResponseData.buildErrorResponse(AdminErrorEnum.PARAM_ERROR.getCode(), AdminErrorEnum.PARAM_ERROR.getMsg());
        }
        boolean userCheckMobile = SERVER_CONFIG.getSafety().getUserCheckMobile();
        if (userCheckMobile && StringUtils.isEmpty(loginParam.getCode())) {
            return ResponseData.buildErrorResponse(AdminErrorEnum.PARAM_ERROR.getCode(), AdminErrorEnum.PARAM_ERROR.getMsg());
        }
        // 解密参数
        String userAccount = RandomCipherUtils.randomDecrypt(loginParam.getUserAccount());
        String password = RandomCipherUtils.randomDecrypt(loginParam.getPassword());
        // 校验登陆次数
        Integer count = countLoginError(userAccount);
        if (count >= MagicConstant.INT_FIVE) {
            AdminErrorEnum loginFreeze = AdminErrorEnum.LOGIN_FREEZE;
            return ResponseData.buildErrorResponse(loginFreeze.getCode(), loginFreeze.getMsg());
        }
        // 校验验证码
        if (userCheckMobile) {
            String code = RandomCipherUtils.randomDecrypt(loginParam.getCode());
            SysUserDto sysUserDto = sysUserService.get(MapUtils.build("userAccount", userAccount)).getData();
            if (sysUserDto != null) {
                Map<String, Object> map = MapUtils.build(8);
                map.put("mobile", sysUserDto.getUserMobile());
                map.put("identifyCode", code);
                map.put("status", CommonConstant.IDENTIFY_CODE_STATUS_UNUSED);
                map.put("appid", loginParam.getAppid());
                CommonIdentifyDto identifyDto = commonSmsService.get(map).getData();
                if (identifyDto == null) {
                    AdminErrorEnum identifyError = AdminErrorEnum.IDENTIFY_ERROR;
                    return ResponseData.buildErrorResponse(identifyError.getCode(), identifyError.getMsg());
                }
                POOL.execute(() -> {
                    identifyDto.setStatus(CommonConstant.IDENTIFY_CODE_STATUS_USED);
                    commonSmsService.edit(identifyDto);
                });
            }
        }
        //执行登陆逻辑
        Map<String, Object> map = MapUtils.build(4);
        map.put("userAccount", userAccount);
        map.put("password", Md5Utils.encode(password));
        ResponseData<List<SysUserDto>> responseData = sysUserService.list(map);
        // 登陆成功
        if (responseData.isSuccess() && CollectionUtils.isNotEmpty(responseData.getData())) {
            List<SysUserDto> userList = responseData.getData();
            // 判断账号重复
            if (userList.size() > 1) {
                AdminErrorEnum loginDuplicateAccount = AdminErrorEnum.LOGIN_DUPLICATE_ACCOUNT;
                return ResponseData.buildErrorResponse(loginDuplicateAccount.getCode(), loginDuplicateAccount.getMsg());
            }
            // 判断账号状态
            SysUserDto sysUserDto = userList.get(0);
            if (!SysUserDto.STATUS_NORMAL.equals(sysUserDto.getStatus())) {
                return ResponseData.buildErrorResponse(AdminErrorEnum.USER_STATUS_FREEZE.getCode(), AdminErrorEnum.USER_STATUS_FREEZE.getMsg());
            }
            // 判断账号是否已经在其他设备登陆
            String loginUserAccessToken = redisStringCacheService.get(LOGIN_USER_PREFIX + sysUserDto.getUuid());
            if (StringUtils.isNotEmpty(loginUserAccessToken)) {
                String loginUserRefreshToken = redisStringCacheService.get(ACCESS_TOKEN_PREFFIX + Md5Utils.encode(loginUserAccessToken));
                String accessToken = redisStringCacheService.get(LOGIN_USER_PREFIX + sysUserDto.getUuid());
                //删除同一账号其他设备上的登陆信息
                redisStringCacheService.del(LOGIN_USER_PREFIX + sysUserDto.getUuid());
                redisStringCacheService.del(ACCESS_TOKEN_PREFFIX + Md5Utils.encode(loginUserAccessToken));
                redisStringCacheService.del(REFRESH_TOKEN_PREFFIX + loginUserRefreshToken);
                redisStringCacheService.del(accessToken, false);
            }

            // 首次登陆
            if (sysUserDto.getFirstLogin() == null || sysUserDto.getFirstLogin() == 0) {
                AdminErrorEnum loginResetPassword = AdminErrorEnum.LOGIN_RESET_PASSWORD;
                return ResponseData.buildErrorResponse(loginResetPassword.getCode(), loginResetPassword.getMsg());
            }
            // 三个月未重置密码必须重置
//            Calendar calendar = Calendar.getInstance();
//            calendar.setTime(sysUserDto.getResetPasswordDate());
//            calendar.add(Calendar.MONTH, 3);
//            if (calendar.getTime().getTime() < System.currentTimeMillis()) {
//                AdminErrorEnum loginPasswordExpired = AdminErrorEnum.LOGIN_PASSWORD_EXPIRED;
//                return ResponseData.buildErrorResponse(loginPasswordExpired.getCode(), loginPasswordExpired.getMsg());
//            }
            // 插入登陆日志
            POOL.execute(() -> {
                SysUserOperateLogDto userOperateLogDto = new SysUserOperateLogDto();
                userOperateLogDto.setUuid(StringUtils.buildUUID());
                userOperateLogDto.setUserUuid(sysUserDto.getUuid());
                userOperateLogDto.setAppid(sysUserDto.getAppid());
                userOperateLogDto.setType(SysUserOperateLogDto.LOGIN);
                userOperateLogDto.setOperationRemark("后台管理登陆");
                userOperateLogDto.setOperationIp(NetworkUtil.getIpAddress(request));
                userOperateLogDto.setCreateOn(new Date());
                userOperateLogDto.setCreateBy(sysUserDto.getUuid());
                sysUserOperateLogService.add(userOperateLogDto);
            });
            // 生成 JWTs 信息
            return ResponseData.buildSuccessResponse(parseLoginVo(sysUserDto, loginParam.getMacInfo(), NetworkUtil.getIpAddress(request)));
        }
        //登陆失败
        else {
            String key = LOGIN_ERROR_PREFIX + userAccount;
            redisStringCacheService.set(key, count + 1 + "", 5);
            return ResponseData.buildErrorResponse(AdminErrorEnum.LOGIN_ERROR.getCode(), AdminErrorEnum.LOGIN_ERROR.getMsg());
        }
    }

    @RequestMapping(value = "/refreshToken", method = RequestMethod.POST)
    @ApiOperation(value = "用户刷新token接口", httpMethod = "POST")
    public ResponseData<AdminLoginVo> refreshToken(@RequestBody AdminRefreshTokenParam refreshTokenParam, HttpServletRequest request) {
        String mapInfo = redisStringCacheService.get(REFRESH_TOKEN_PREFFIX + refreshTokenParam.getRefreshToken());
        // 增加一次重试
        if (StringUtils.isEmpty(mapInfo)) {
            mapInfo = redisStringCacheService.get(REFRESH_TOKEN_PREFFIX + refreshTokenParam.getRefreshToken());
        }
        if (StringUtils.isNotEmpty(mapInfo)) {
            try {
                Map<String, Object> map = JsonUtils.readValue(mapInfo, Map.class);
                // 解析 jwt 信息
                String jwtInfo = String.valueOf(map.get("accessToken"));
                // 解析上次登录设备信息
                String macInfo = String.valueOf(map.get("macInfo")).trim();
                // 删除原先 token 信息
                redisStringCacheService.del(REFRESH_TOKEN_PREFFIX + refreshTokenParam.getRefreshToken());
                redisStringCacheService.del(ACCESS_TOKEN_PREFFIX + Md5Utils.encode(jwtInfo));

                if (!macInfo.equals(refreshTokenParam.getMacInfo().trim())) {
                    AdminErrorEnum loginNoSafety = AdminErrorEnum.LOGIN_NO_SAFETY;
                    return ResponseData.buildErrorResponse(loginNoSafety.getCode(), loginNoSafety.getMsg());
                }
                AdminParam adminParam = JsonUtils.readValue(JwtHelper.parseJWT(jwtInfo), AdminParam.class);
                ResponseData<SysUserDto> responseData = sysUserService.get(adminParam.getUserUuid());
                if (responseData.isSuccess() && responseData.getData() != null) {
                    SysUserDto sysUserDto = responseData.getData();
                    if (!SysUserDto.STATUS_NORMAL.equals(sysUserDto.getStatus())) {
                        return ResponseData.buildErrorResponse(AdminErrorEnum.USER_STATUS_FREEZE.getCode(), AdminErrorEnum.USER_STATUS_FREEZE.getMsg());
                    }
                    // 生成 JWTs 信息
                    return ResponseData.buildSuccessResponse(parseLoginVo(sysUserDto, refreshTokenParam.getMacInfo(), NetworkUtil.getIpAddress(request)));
                }
            } catch (IOException e) {
                log.error("LogicAdmin refreshToken readValue error.", e);
            }
        }
        return ResponseData.buildErrorResponse(AdminErrorEnum.LOGIN_EXPIRED.getCode(), AdminErrorEnum.LOGIN_EXPIRED.getMsg());
    }


    @RequestMapping(value = "/identify", method = RequestMethod.POST)
    @ApiOperation(value = "用户获取验证码接口", httpMethod = "POST")
    public ResponseData<Boolean> identify(@RequestBody AdminIdentifyParam identifyParam) {
        if (ParamUtil.isIllegal(identifyParam.getUserAccount())) {
            return ResponseData.buildErrorResponse(AdminErrorEnum.PARAM_ERROR.getCode(), AdminErrorEnum.PARAM_ERROR.getMsg());
        }
        // 校验登陆次数
        if (countLoginError(identifyParam.getUserAccount()) >= MagicConstant.INT_FIVE) {
            AdminErrorEnum loginFreeze = AdminErrorEnum.LOGIN_FREEZE;
            return ResponseData.buildErrorResponse(loginFreeze.getCode(), loginFreeze.getMsg());
        }
        SysUserDto sysUserDto = sysUserService.get(MapUtils.build("userAccount", identifyParam.getUserAccount())).getData();
        if (sysUserDto == null || StringUtils.isEmpty(sysUserDto.getUserMobile())) {
            AdminErrorEnum identifySendError = AdminErrorEnum.IDENTIFY_SEND_ERROR;
            return ResponseData.buildErrorResponse(identifySendError.getCode(), identifySendError.getMsg());
        }
        //发送验证码
        ResponseData<Boolean> sendIdentify = commonSmsService.sendAliIdentify(sysUserDto.getUserMobile(), sysUserDto.getAppid());
        if (sendIdentify.isSuccess()) {
            return ResponseData.buildSuccessResponse("发送成功，请注意查收", Boolean.TRUE);
        }
        return ResponseData.buildErrorResponse(sendIdentify.getErrCode(), "发送失败", Boolean.FALSE);
    }

    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    @ApiOperation(value = "用户登出接口", httpMethod = "POST")
    public ResponseData<Boolean> logout(@RequestBody AdminParam adminParam) {
        //删除账号信息
        String loginUserAccessToken = redisStringCacheService.get(LOGIN_USER_PREFIX + adminParam.getUserUuid());
        if (StringUtils.isNotEmpty(loginUserAccessToken)) {
            String loginUserRefreshToken = redisStringCacheService.get(ACCESS_TOKEN_PREFFIX + Md5Utils.encode(loginUserAccessToken));
            //删除同一账号其他设备上的登陆信息
            redisStringCacheService.del(LOGIN_USER_PREFIX + adminParam.getUserUuid());
            redisStringCacheService.del(ACCESS_TOKEN_PREFFIX + Md5Utils.encode(loginUserAccessToken));
            redisStringCacheService.del(REFRESH_TOKEN_PREFFIX + loginUserRefreshToken);
            redisStringCacheService.del(loginUserAccessToken, false);
        }
        return ResponseData.buildSuccessResponse("退出成功", true);
    }

    /**
     * 统计登陆次数
     *
     * @param userAccount
     * @return
     */
    private Integer countLoginError(String userAccount) {
        String key = LOGIN_ERROR_PREFIX + userAccount;
        String count = redisStringCacheService.get(key);
        if (StringUtils.isNotEmpty(count)) {
            return Integer.parseInt(count);
        }
        return 0;
    }

    /**
     * 用户信息转换成登陆信息
     *
     * @param sysUserDto
     * @param macInfo
     * @param ipAddress
     * @return
     */
    private AdminLoginVo parseLoginVo(SysUserDto sysUserDto, String macInfo, String ipAddress) {
        AdminParam adminParam = new AdminParam();
        adminParam.setUserUuid(sysUserDto.getUuid());
        adminParam.setAppid(sysUserDto.getAppid());
        adminParam.setUserAgentUuid(sysUserDto.getAgentUuid());
        adminParam.setUserCompanyUuid(sysUserDto.getCompanyUuid());
        adminParam.setUserip(ipAddress);
        adminParam.setUserRoleUuid(sysUserDto.getUserRole());
        adminParam.setUserType(sysUserDto.getUserType());
        String accessToken = JwtHelper.createJWT(sysUserDto.getUuid(), JsonUtils.toJSONString(adminParam));
        String refreshToken = StringUtils.buildUUID();
        redisStringCacheService.set(LOGIN_USER_PREFIX + sysUserDto.getUuid(), accessToken, ACCESS_TOKEN_EXPIRATION);
        //将token存入key 作为后台长连接登录判断依据
        redisStringCacheService.set(accessToken, "1", ACCESS_TOKEN_EXPIRATION, TimeUnit.MINUTES, false);
        redisStringCacheService.set(ACCESS_TOKEN_PREFFIX + Md5Utils.encode(accessToken), refreshToken, ACCESS_TOKEN_EXPIRATION);
        Map<String, Object> map = new HashMap<>(4);
        map.put("accessToken", accessToken);
        map.put("macInfo", macInfo);
        redisStringCacheService.set(REFRESH_TOKEN_PREFFIX + refreshToken, JsonUtils.toJSONString(map), REFRESH_TOKEN_EXPIRATION);
        //删除用户登录错误次数
        redisStringCacheService.del(LOGIN_ERROR_PREFIX + sysUserDto.getUserAccount());
        AdminLoginVo adminLoginVo = new AdminLoginVo();
        adminLoginVo.setUserLevel(setUserLevel(sysUserDto));
        adminLoginVo.setUserName(sysUserDto.getUserName());
        adminLoginVo.setUserAccount(sysUserDto.getUserAccount());
        adminLoginVo.setUserType(sysUserDto.getUserType());
        adminLoginVo.setFace(sysUserDto.getFace());
        adminLoginVo.setAccessToken(accessToken);
        adminLoginVo.setRefreshToken(refreshToken);
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, ACCESS_TOKEN_EXPIRATION);
        adminLoginVo.setExpiration(calendar.getTime());
        // 用户的业务列表
        String organizationUuid = "";
        if (StringUtils.isNotEmpty(sysUserDto.getAppid())) {
            organizationUuid = sysUserDto.getAppid();
        }
        if (StringUtils.isNotEmpty(sysUserDto.getAgentUuid())) {
            organizationUuid = sysUserDto.getAgentUuid();
        }
        if (StringUtils.isNotEmpty(sysUserDto.getCompanyUuid())) {
            organizationUuid = sysUserDto.getCompanyUuid();
        }

        ResponseData<List<BusinessVo>> getBusiness = sysBusinessOrganizationService.getBusiness(MapUtils.build("organizationUuid", organizationUuid));
        if (getBusiness.isSuccess() && CollectionUtils.isNotEmpty(getBusiness.getData())) {
            List<BusinessVo> expandBusinesses = getBusiness.getData();
            adminLoginVo.setExpandBusinesses(expandBusinesses);

            ArrayList<BusinessVo> businessVos = new ArrayList<>(expandBusinesses);
            // 跨城小件作为跨城拼车的扩展业务
            BusinessVo pancel = businessVos.stream()
                    .filter(businessVo -> businessVo.getType() == CommonConstant.BUSINESS_TYPE_PARCEL)
                    .findFirst().orElse(null);
            if (pancel != null) {
                businessVos.remove(pancel);
            }
            adminLoginVo.setBaseBusinesses(businessVos);
        }
        // 用户的资源列表
        ResponseData<List<SysResourceDto>> queryUserAuthority = sysUserService.queryUserAuthority(sysUserDto.getUuid());
        if (queryUserAuthority.isSuccess() && CollectionUtils.isNotEmpty(queryUserAuthority.getData())) {
            List<SysResourceDto> resourceList = queryUserAuthority.getData();
            adminLoginVo.setResourceList(resourceList);
            // 资源树
            List<SysResourceDto> treeResource = buildAuthorityTree(resourceList, "0");
            adminLoginVo.setTreeResource(treeResource);
            // 按钮资源
            List<SysResourceDto> buttonResources = resourceList.stream()
                    .filter(resource -> resource.getResourceType() == 2).collect(Collectors.toList());
            List<String> buttonUuids = buttonResources.stream().map(sysResourceDto -> sysResourceDto.getUuid()).collect(Collectors.toList());
            // 没有权限的按钮资源
            ResponseData<List<SysResourceDto>> queryAuthHideBtn = sysResourceService.queryAuthHideBtn(MapUtils.build("existBtnIds", buttonUuids));
            if (queryAuthHideBtn.isSuccess() && CollectionUtils.isNotEmpty(queryAuthHideBtn.getData())) {
                List<SysResourceDto> hideBtns = queryAuthHideBtn.getData();
                adminLoginVo.setHideBtns(hideBtns);
            }
        }
        return adminLoginVo;
    }

    /**
     * 设置用户级别 1平台 2代理 3公司
     *
     * @param sysUserDto
     * @return
     */
    private Integer setUserLevel(SysUserDto sysUserDto) {
        if (StringUtils.isNotEmpty(sysUserDto.getAgentUuid()) && StringUtils.isEmpty(sysUserDto.getCompanyUuid())) {
            return 2;
        } else if (StringUtils.isNotEmpty(sysUserDto.getCompanyUuid())) {
            return 3;
        } else {
            return 1;
        }
    }

    /**
     * 构建菜单资源树
     *
     * @param resourceList 菜单资源列表
     * @param parentId     父级id
     * @return List<SysResourceDto>
     */
    private List<SysResourceDto> buildAuthorityTree(List<SysResourceDto> resourceList, String parentId) {
        List<SysResourceDto> buildRes = new ArrayList<>();
        for (SysResourceDto sysResourceDto : resourceList) {
            if (sysResourceDto != null) {
                if (parentId.equals(sysResourceDto.getResourceParent())) {
                    sysResourceDto.setSysResourceDtoList(buildAuthorityTree(resourceList, sysResourceDto.getUuid()));
                    buildRes.add(sysResourceDto);
                }
            }
        }
        buildRes.sort(Comparator.comparingInt(SysResourceDto::getResourceSort));
        return buildRes;
    }
}
